package abel.wlmr.config;

import java.io.Closeable;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import abel.util.HexString;
import abel.util.StdBcdNumber;
import abel.wlmr.model.type.MeterConfigBody;
import abel.wlmr.model.type.WlmrAddrBody;

public final class MsaConfigLoader {

    private static final String CONFIG_YXD_JSON = "config/yxd.json";

    private static final String CONFIG_MX_JSON = "config/mx.json";

    private static final String CONFIG_HAT_JSON = "config/hat.json";

    private static final Logger log = LoggerFactory.getLogger(MsaConfig.class);

    private static final int LOAD_CONFIG_TIMEOUT = 1;

    private static final ReentrantReadWriteLock rw = new ReentrantReadWriteLock();

    private static final String JSON_FILE = "msa_config.json";

    private static MsaConfig msaConfig;

    /** last modified of msa config. */
    private static DateTime msaTime;

    private static Map<String, MeterStation> statMap = Collections.synchronizedMap(new HashMap<>());

    /** last modified for config of every station. */
    private static Map<String, DateTime> statTime = Collections.synchronizedMap(new HashMap<>());

    public static void main(String[] args) throws IOException {
        buildDemoConfig();

        checkConfigFiles();

        // reload again.
        checkConfigFiles();
    }

    /**
     * This method is assured to be thread-safe.
     * 
     * @return
     */
    public static MsaConfig getMsaConfig() {
        try {
            rw.readLock().lock();

            return msaConfig;

        } finally {
            rw.readLock().unlock();
        }
    }

    /**
     * This method is assured to be thread-safe.
     * 
     * @return
     */
    public static Map<String, MeterStation> getStationMap() {
        try {
            rw.readLock().lock();

            return statMap;

        } finally {
            rw.readLock().unlock();
        }
    }

    /**
     * called during idle handler.
     * 
     * @param ctx
     */
    public static void checkConfigFiles() {
        // check main.
        {
            FileReader json = null;

            try {
                // to avoid re-load again during loading.
                if (rw.writeLock().tryLock(LOAD_CONFIG_TIMEOUT, TimeUnit.SECONDS)) {
                    // time
                    File msaFile = new File(JSON_FILE);
                    DateTime dt = new DateTime(msaFile.lastModified());

                    if (!dt.equals(msaTime)) {
                        json = new FileReader(JSON_FILE);
                        msaConfig = new Gson().fromJson(json, MsaConfig.class);

                        // set some initial values.
                        checkMsaConfig(msaConfig);

                        msaTime = dt;

                        log.info("MSA config at time {}: {}", msaTime, msaConfig);
                    }
                }
            } catch (Exception e) {
                // log it.
                log.error("FAIL to load main & station config caused by " + e.getMessage(), e);
            } finally {
                closeForciably(json);

                rw.writeLock().unlock();
            }
        }

        // check station list.
        {
            Set<String> statList = getMsaConfig().getStatList();

            // check one by one
            for (String file : statList) {
                checkStationFile(file);
            } // check station 1 by 1.
        } // check station list

    }

    private static void checkMsaConfig(MsaConfig mc) {
        if (mc.getQueryPeriod() < 1) {
            mc.setQueryPeriod(180);
        }

        if (mc.getHeartPeriod() < 1) {
            mc.setHeartPeriod(10);
        }

        if (mc.getFirstTaskMinutes() < 1) {
            mc.setFirstTaskMinutes(9);
        }

        if (mc.getFirstDataMinutes() < 1) {
            mc.setFirstDataMinutes(10);
        }

        if (mc.getSecondTaskMinutes() < 1) {
            mc.setSecondTaskMinutes(11);
        }

        if (mc.getSecondDataMinutes() < 1) {
            mc.setSecondDataMinutes(12);
        }
    }

    /**
     * @param file
     */
    public static void checkStationFile(String file) {
        FileReader rd = null;
        Gson gson = new GsonBuilder().setPrettyPrinting().create();

        try {
            // to avoid re-load again during loading.
            if (!rw.writeLock().tryLock(LOAD_CONFIG_TIMEOUT, TimeUnit.SECONDS)) {
                return;
            }

            File f = new File(file);
            DateTime dt = new DateTime(f.lastModified());

            MeterStation station = null;

            if (!dt.equals(statTime.get(file))) {
                rd = new FileReader(file);
                station = gson.fromJson(rd, MeterStation.class);

                if (station != null) {
                    getStationMap().put(station.getAddr().stationKey(), station);
                    statTime.put(file, dt);
                }

                log.info("load station config at {} for {}: {}", dt, file, gson.toJson(station));
            } // if there's date time for station
        } catch (Exception e) {
            // just log
            log.error("FAIL to load config " + file, e);
        } finally {
            closeForciably(rd);

            rw.writeLock().unlock();
        }
    }

    /**
     * @param json
     * @throws IOException
     */
    private static void closeForciably(Closeable json) {
        if (json != null) {
            try {
                json.close();
            } catch (IOException e) {
                // does nothing.
            }
        }
    }

    /**
     * Just build demo config. Should never be called during production.
     * 
     * @throws IOException
     */
    private static void buildDemoConfig() throws IOException {
        // main station.
        MsaConfig cfg = new MsaConfig();

        cfg.getMain().setA1(new StdBcdNumber("5180"));
        cfg.getMain().getA3().setAddr((byte) 101);

        cfg.getStatList().add(CONFIG_HAT_JSON);
        cfg.getStatList().add(CONFIG_MX_JSON);
        cfg.getStatList().add(CONFIG_YXD_JSON);

        FileWriter fw = null;
        try {
            fw = new FileWriter(JSON_FILE);
            fw.write(new Gson().toJson(cfg));
        } finally {
            closeForciably(fw);
        }

        // Hua Ao Tong
        {
            MeterStation station = new MeterStation();
            station.setPwd("1020304050607080");
            station.setAddr(new WlmrAddrBody());
            station.getAddr().setA1(new StdBcdNumber("5180"));
            station.getAddr().setA2(10001);

            // meter1
            {
                MeterConfigBody meter1 = new MeterConfigBody();
                meter1.setMeterNo((short) 1);
                meter1.setDaNo((short) 1);
                meter1.setMeterAddr(new StdBcdNumber("10203040506010"));
                meter1.setMeterPwd(new HexString("414243444546"));
                meter1.setParentAddr(new StdBcdNumber("102030405060"));

                station.getMeterList().add(meter1);
            }

            // meter 2
            {
                MeterConfigBody meter1 = new MeterConfigBody();
                meter1.setMeterNo((short) 2);
                meter1.setDaNo((short) 2);
                meter1.setMeterAddr(new StdBcdNumber("10203040506020"));
                meter1.setMeterPwd(new HexString("414243444546"));
                meter1.setParentAddr(new StdBcdNumber("102030405060"));

                station.getMeterList().add(meter1);
            }

            // meter 3
            {
                MeterConfigBody meter1 = new MeterConfigBody();
                meter1.setMeterNo((short) 9);
                meter1.setDaNo((short) 9);
                meter1.setMeterAddr(new StdBcdNumber("10203040506030"));
                meter1.setMeterPwd(new HexString("414243444546"));
                meter1.setParentAddr(new StdBcdNumber("102030405060"));

                station.getMeterList().add(meter1);
            }

            try {
                fw = new FileWriter(CONFIG_HAT_JSON);
                fw.write(new Gson().toJson(station));
            } finally {
                closeForciably(fw);
            }
        }

        // Mai Xi
        {
            MeterStation station = new MeterStation();
            station.setPwd("2020304050607080");
            station.setAddr(new WlmrAddrBody());
            station.getAddr().setA1(new StdBcdNumber("5180"));
            station.getAddr().setA2(20001);

            // meter1
            {
                MeterConfigBody meter1 = new MeterConfigBody();
                meter1.setMeterNo((short) 1);
                meter1.setDaNo((short) 1);
                meter1.setMeterAddr(new StdBcdNumber("20203040506010"));
                meter1.setMeterPwd(new HexString("424344454647"));
                meter1.setParentAddr(new StdBcdNumber("202030405060"));

                station.getMeterList().add(meter1);
            }

            // meter 2
            {
                MeterConfigBody meter1 = new MeterConfigBody();
                meter1.setMeterNo((short) 17);
                meter1.setDaNo((short) 17);
                meter1.setMeterAddr(new StdBcdNumber("20203040506020"));
                meter1.setMeterPwd(new HexString("424344454647"));
                meter1.setParentAddr(new StdBcdNumber("202030405060"));

                station.getMeterList().add(meter1);
            }

            // meter 3
            {
                MeterConfigBody meter1 = new MeterConfigBody();
                meter1.setMeterNo((short) 18);
                meter1.setDaNo((short) 18);
                meter1.setMeterAddr(new StdBcdNumber("20203040506030"));
                meter1.setMeterPwd(new HexString("424344454647"));
                meter1.setParentAddr(new StdBcdNumber("202030405060"));

                station.getMeterList().add(meter1);
            }

            try {
                fw = new FileWriter(CONFIG_MX_JSON);
                fw.write(new Gson().toJson(station));
            } finally {
                closeForciably(fw);
            }
        }

        // You Xun Da
        {
            MeterStation station = new MeterStation();
            station.setPwd("3020304050607080");
            station.setAddr(new WlmrAddrBody());
            station.getAddr().setA1(new StdBcdNumber("5180"));
            station.getAddr().setA2(30001);

            // meter1
            {
                MeterConfigBody meter1 = new MeterConfigBody();
                meter1.setMeterNo((short) 1);
                meter1.setDaNo((short) 1);
                meter1.setMeterAddr(new StdBcdNumber("30203040506010"));
                meter1.setMeterPwd(new HexString("434445464748"));
                meter1.setParentAddr(new StdBcdNumber("302030405060"));

                station.getMeterList().add(meter1);
            }

            // meter 2
            {
                MeterConfigBody meter1 = new MeterConfigBody();
                meter1.setMeterNo((short) 21);
                meter1.setDaNo((short) 21);
                meter1.setMeterAddr(new StdBcdNumber("30203040506020"));
                meter1.setMeterPwd(new HexString("43 44 45 46 47 48"));
                meter1.setParentAddr(new StdBcdNumber("302030405060"));

                station.getMeterList().add(meter1);
            }

            // meter 3
            {
                MeterConfigBody meter1 = new MeterConfigBody();
                meter1.setMeterNo((short) 31);
                meter1.setDaNo((short) 31);
                meter1.setMeterAddr(new StdBcdNumber("30203040506030"));
                meter1.setMeterPwd(new HexString("434445464748"));
                meter1.setParentAddr(new StdBcdNumber("302030405060"));

                station.getMeterList().add(meter1);
            }

            try {
                fw = new FileWriter(CONFIG_YXD_JSON);
                fw.write(new Gson().toJson(station));
            } finally {
                closeForciably(fw);
            }
        }

    }
}
